﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
namespace NGE.Net
{
    public delegate void DatagramHandlerEvent(byte[] data);
    public delegate void SessionEvent(object sender, object arg);
    public class DatagramHandler
    {
        public DatagramHandler()
        {
            sTag = new byte[] { 0xff, 0xaa, 0x55, 0x00 };
            eTag = new byte[] { 0x00, 0x55, 0xaa, 0xff };
        }
        public DatagramHandler(string startTag, string endTag)
        {
            sTag = Encoding.Default.GetBytes(startTag);
            eTag = Encoding.Default.GetBytes(endTag);
        }
        public DatagramHandler(byte[] startTag, byte[] endTag)
        {
            sTag = startTag;
            eTag = endTag;
        }

        byte[] sTag;
        byte[] eTag;
        byte[] buf;
        //byte[] buf2;
        //bool isHandling;

        public event DatagramHandlerEvent OnRelease;

        byte[] method_depack_1 (ref byte[] data)
        {
            int startIndex = find(data, sTag);
            int endIndex = find(data, eTag);

            if (startIndex == -1)
            {
                return null;
            }
            if (endIndex == -1)
            {
                return null;
            }

            byte[] tbuf = new byte[sizeof(int)];
            tbuf = cut(data, startIndex + sTag.Length, tbuf.Length);
            int slen = -1;
            for (int i = 0; i < tbuf.Length; i++)
            {
                slen <<= 8;
                slen |= tbuf[tbuf.Length - 1 - i];
            }
            tbuf = cut(data, endIndex - tbuf.Length, tbuf.Length);
            int elen = -2;
            for (int i = 0; i < tbuf.Length; i++)
            {
                elen <<= 8;
                elen |= tbuf[tbuf.Length - 1 - i];
            }

            if (slen == elen)
            {
                byte[] one = cut(data, 0, startIndex);
                byte[] two = cut(data, one.Length + sTag.Length + sizeof(int), endIndex - startIndex - sTag.Length - sizeof(int) - sizeof(int));
                byte[] three = cut(data, endIndex + eTag.Length);
                data = combine(one, three);
                return two;
            }

            return null;
        }
        byte[] method_pack_1(byte[] data)
        {           
            byte[] gendata;
            byte[] len = BitConverter.GetBytes(data.Length);
            gendata = combine(sTag, len);
            gendata = combine(gendata, data);
            gendata = combine(gendata, len);
            gendata = combine(gendata, eTag);
            return gendata;
        }

        byte[] method_depack_2(ref byte[] data)
        {
            byte[] one;
            byte[] two;
            byte[] three;
            int startIndex = find(data, sTag);
            if (startIndex == -1)
            {
                return null;
            }
            //Debug.Assert(startIndex > 0);

            byte[] tbuf = new byte[sizeof(int)];
            tbuf = cut(data, startIndex + sTag.Length, tbuf.Length);
            if (tbuf == null)
            {
                return null;
            }
            int slen = -1;
            for (int i = 0; i < tbuf.Length; i++)
            {
                slen <<= 8;
                slen |= tbuf[tbuf.Length - 1 - i];
            }
            if (slen > data.Length - sizeof(int) - sTag.Length)
            {
                return null;
            }

            one = cut(data, 0, startIndex);
            
            two = cut(data, one.Length + sTag.Length + sizeof(int), slen);
            three = cut(data, one.Length + sTag.Length + sizeof(int) + slen);
            data = combine(one, three);

            return two;
        }
        byte[] method_pack_2(byte[] data)
        {
            byte[] gendata;
            byte[] len = BitConverter.GetBytes(data.Length);
            gendata = combine(sTag, len);
            gendata = combine(gendata, data);
            return gendata;
        }


        public void Input(byte[] data)
        {
            buf = combine(buf, data);

            while(true)
            {
                byte[] datagram = method_depack_2(ref buf);

                if (datagram != null)
                {
                    if (OnRelease != null)
                    {
                        OnRelease(datagram);
                    }
                }
                else
                {
                    break;
                }
            }
        }

        public byte[] Generate(byte[] data)
        {
            return method_pack_2(data);
        }

        //int DataLength
        //{
        //    get
        //    {
        //        return buf.Length - sTag.Length - eTag.Length - sizeof(int) * 2;
        //    }
        //}
        
        byte[] combine(byte[] src1, byte[] src2)
        {
            if (src1 == null && src2 != null)
            {
                return src2;
            }
            else if (src2 == null && src1 != null)
            {
                return src1;
            }
            else if (src1 == null && src2 == null)
            {
                return null;
            }
            byte[] tmp = new byte[src1.Length + src2.Length];
            for (int i = 0; i < src1.Length; i++)
            {
                tmp[i] = src1[i];
            }
            for (int i = 0; i < src2.Length; i++)
            {
                tmp[i + src1.Length] = src2[i];
            }
            return tmp;
        }
        int find(byte[] src, byte[] dest)
        {
            for (int i = 0; i < src.Length; i++)
            {
                if (src[i] == dest[0])
                {
                    if (i + dest.Length <= src.Length)
                    {
                        bool passed = true;
                        for (int j = 1; j < dest.Length; j++)
                        {
                            if (src[j + i] != dest[j])
                            {
                                passed = false;
                                break;
                            }
                        }
                        if (passed)
                        {
                            return i;
                        }
                    }
                }
            }
            return -1;
        }
        byte[] cut(byte[] srcbyte, int offset, int count)
        {
            if (offset + count > srcbyte.Length)
            {
                return null;
            }
            byte[] dstbyte = new byte[count];
            int c;
            c = 0;
            while (c < count)
            {
                dstbyte[c] = srcbyte[offset + c];
                c++;
            }
            return dstbyte;
        }
        byte[] cut(byte[] srcbyte, int offset)
        {
            if (offset > srcbyte.Length)
            {
                return null;
            }
            byte[] dstbyte = new byte[srcbyte.Length - offset];
            int c;
            c = 0;
            while (c < dstbyte.Length)
            {
                dstbyte[c] = srcbyte[offset + c];
                c++;
            }
            return dstbyte;
        }
    }

    public class Client
    {
        public Client()
            : this(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),-1)
        {
        }
        public Client(Socket s,int handle)
        {
            socket = s;
            this.handle = handle;

            datagramHandler = new DatagramHandler();
            datagramHandler.OnRelease += new DatagramHandlerEvent(datagramHandler_OnRelease);
            if (OnConnected != null)
            {
                OnConnected(this, null);
            }
        }

        public event SessionEvent OnConnected;
        public event SessionEvent OnDisconnected;
        public event SessionEvent OnSent;
        public event SessionEvent OnReceived;

        Socket socket;
        DatagramHandler datagramHandler;
        byte[] sbuf;
        byte[] rbuf;
        int handle;
        public int Handle
        {
            get
            {
                return handle;
            }
        }
        public bool IsConnected
        {
            get
            {
                return socket.Connected;
            }
        }
        public string RemoteAddress
        {
            get
            {
                return socket.RemoteEndPoint.ToString();
            }
        }
        public string LocalAddress
        {
            get
            {
                return socket.LocalEndPoint.ToString();
            }
        }

        public void Send(byte[] data)
        {
            sbuf = datagramHandler.Generate(data);
            socket.BeginSend(sbuf, 0, sbuf.Length, SocketFlags.None, new AsyncCallback(sendCallback), socket);
        }
        void sendCallback(IAsyncResult asyncResult)
        {
            Socket s = ((Socket)asyncResult.AsyncState);
            int c = s.EndSend(asyncResult);
            //if (c != sbuf.Length)
            //{
            //    int l = sbuf.Length - c;
            //    byte[] tbuf = new byte[l];
            //    Array.Copy(sbuf, c, tbuf, 0, l);
            //    sbuf = tbuf;
            //    socket.BeginSend(sbuf, c, l, SocketFlags.None, new AsyncCallback(sendCallback), socket);
            //}

            if (OnSent != null)
            {
                OnSent(this, null);
            }
            Debug.Assert(c != 0);

        }
        //public void Receive()
        //{
        //    rbuf = new byte[socket.ReceiveBufferSize];
        //    socket.BeginReceive(rbuf, 0, rbuf.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
        //}
        void receiveCallback(IAsyncResult asyncResult)
        {
            try
            {
                Socket s = ((Socket)asyncResult.AsyncState);
                int c = s.EndReceive(asyncResult);

                byte[] tmp = new byte[c];
                Array.Copy(rbuf, tmp, c);
                datagramHandler.Input(tmp);
                socket.BeginReceive(rbuf, 0, rbuf.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
            }
            catch (SocketException ex)
            {
                if (ex.ErrorCode == 10054)
                {
                    //客户端强制关闭
                    if (OnDisconnected != null)
                    {
                        OnDisconnected(this, null);
                    }
                }
            }
            catch (ObjectDisposedException ex)
            {
                //这里的实现不够优雅
                //当调用CloseSession()时,会结束数据接收,但是数据接收
                //处理中会调用int recv = client.EndReceive(iar);
                //就访问了CloseSession()已经处置的对象
                //我想这样的实现方法也是无伤大雅的.
                if (ex != null)
                {
                    ex = null;
                }
                if (OnDisconnected != null)
                {
                    OnDisconnected(this, null);
                }
            }
            //if (c != rbuf.Length)
            //{
            //    int l = rbuf.Length - c;
            //    socket.BeginReceive(rbuf, c, l, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
            //}
            //else
            //{
            //    datagramHandler.Input(rbuf);
            //    socket.BeginReceive(rbuf, 0, rbuf.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
            //}
        }
        public void StartReceiving()
        {
            rbuf = new byte[socket.ReceiveBufferSize];
            socket.BeginReceive(rbuf, 0, rbuf.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
        }

        public void Connect(string ip, int port)
        {
            IPAddress[] ipa = Dns.GetHostAddresses(ip);
            IPEndPoint iep;
            if (ipa.Length > 0)
            {
                iep = new IPEndPoint(IPAddress.Parse(ipa[ipa.Length - 1].ToString()), port);
            }
            else
            {
                iep = new IPEndPoint(IPAddress.Parse(ip), port);
            }
            socket.Connect(ip, port);

            StartReceiving();

            if (OnConnected != null)
            {
                OnConnected(this, null);
            }
        }
        public void Disconnect()
        {
            socket.Disconnect(true);
            if (OnDisconnected != null)
            {
                OnDisconnected(this, null);
            }
        }

        void datagramHandler_OnRelease(byte[] data)
        {
            if (OnReceived != null)
            {
                OnReceived(this, data);
            }
        }
    }

}
